#include <fhe/core/NodeFactory.h>
#include <fhe/core/Node.h>
#include <fhe/core/FileSystem.h>
#include <gtest/gtest.h>

using namespace fhe;
using namespace core;

TEST( CoreTest, call )
{
  Node::Ptr node = NodeFactory::instance().buildNode( "fhetest", "TestNode" );
  ASSERT_TRUE( node );
  ASSERT_EQ( "fhetest", node->module() );
  ASSERT_EQ( "TestNode", node->type() );
  ASSERT_TRUE( node->is( "TestNode" ) );
  ASSERT_TRUE( node->is( "ITestNode" ) );
  ASSERT_FALSE( node->is( "foo" ) );
  
  node->call( "set", 2 );
  ASSERT_EQ( 2, node->call( "get" ).get<int>() );
  
  ASSERT_EQ( "herro", node->call( "msg" ).get<std::string>() );
}

TEST( CoreTest, publish )
{
  Node::Ptr parent = NodeFactory::instance().buildNode( "core", "Node" );
  Node::Ptr child = NodeFactory::instance().buildNode( "fhetest", "TestNode" );
  
  parent->addChild( child );
  ASSERT_TRUE( parent->hasChild( child ) );
  ASSERT_EQ( parent, child->parent() );
  
  parent->publish( "set", 2 );
  ASSERT_EQ( 2, child->call( "get" ).get<int>() );
  ASSERT_EQ( 2, child->get<int>( "get" ) );
}

TEST( CoreTest, vars )
{
  Node::Ptr node = NodeFactory::instance().buildNode( "fhetest", "TestNode" );
  
  node->set( "s", std::string( "world" ) );
  ASSERT_EQ( "world", node->get<std::string>( "s" ) );
  
  node->set( "j", 2 );
  ASSERT_EQ( 2, node->get<int>( "j" ) );
  ASSERT_EQ( "default", node->get( "notreal", std::string( "default" ) ) );
}

TEST( CoreTest, files )
{
  Node::Ptr node = Node::load( "../tests/CoreTest.xml" );
  ASSERT_TRUE( node );
  ASSERT_EQ( "fhetest", node->module() );
  ASSERT_EQ( "TestNode", node->type() );
  ASSERT_TRUE( node->get<bool>( "b" ) );
  ASSERT_EQ( -100, node->get<int>( "i" ) );
  ASSERT_EQ( 3.14, node->get<double>( "f" ) );
  ASSERT_EQ( "hello xml", node->get<std::string>( "s" ) );
  ASSERT_NE( node->childrenBegin(), node->childrenEnd() );
  Node::Ptr child = *node->childrenBegin();
  ASSERT_EQ( "TestNode", child->type() );
  ASSERT_EQ( 15, child->get<int>( "j" ) );
  
  node->save( "CoreTest2.xml" );
  node = Node::load( "CoreTest2.xml" );
  ASSERT_EQ( "fhetest", node->module() );
  ASSERT_EQ( "TestNode", node->type() );
  ASSERT_EQ( "hello xml", node->get<std::string>( "s" ) );
  ASSERT_NE( node->childrenBegin(), node->childrenEnd() );
  child = *node->childrenBegin();
  ASSERT_EQ( "TestNode", child->type() );
  ASSERT_EQ( 15, child->get<int>( "j" ) );
}

TEST( CoreTest, inheritance )
{
  Node::Ptr node = NodeFactory::instance().buildNode( "fhetest", "ChildTestNode" );
  ASSERT_TRUE( node );
  ASSERT_EQ( "fhetest", node->module() );
  ASSERT_EQ( "ChildTestNode", node->type() );
  ASSERT_TRUE( node->is( "TestNode" ) );
  ASSERT_TRUE( node->is( "ITestNode" ) );
  
  node->set( "j", 14 );
  ASSERT_EQ( 14, node->get<int>( "j" ) );
  
  node->set( "s", std::string( "parentVal" ) );
  ASSERT_EQ( "parentVal", node->get<std::string>( "s" ) );
  
  node->set( "childVar", 11 );
  ASSERT_EQ( 11, node->get<int>( "childVar" ) );
  
  node->call( "childSet", std::string( "childVal" ) );
  ASSERT_EQ( "childVal", node->call( "childGet" ).get<std::string>() );
}

void xmlDataStreamTest( const std::string& filename, const std::string& saveFilename )
{
  Data::Ptr data = FileSystem::instance().load( filename );
  ASSERT_EQ( "root", data->getName() );
  ASSERT_EQ( Data::PARENT, data->getType() );
  ASSERT_TRUE( data->hasChild( "child" ) );
  
  Data::Ptr child( data->getChild( "child" ) );
  ASSERT_EQ( "child", child->getName() );
  ASSERT_EQ( Data::PARENT, child->getType() );
  ASSERT_EQ( 4, child->children().size() );
  ASSERT_TRUE( child->hasChild( "b" ) );
  ASSERT_TRUE( child->hasChild( "i" ) );
  ASSERT_TRUE( child->hasChild( "d" ) );
  ASSERT_TRUE( child->hasChild( "s" ) );
  
  Data::Ptr b( child->getChild( "b" ) );
  ASSERT_EQ( "b", b->getName() );
  ASSERT_EQ( Data::BOOL, b->getType() );
  ASSERT_EQ( 0, b->children().size() );
  ASSERT_EQ( true, b->getBool() );
  
  Data::Ptr i( child->getChild( "i" ) );
  ASSERT_EQ( "i", i->getName() );
  ASSERT_EQ( Data::INT, i->getType() );
  ASSERT_EQ( 0, i->children().size() );
  ASSERT_EQ( -34, i->getInt() );
  
  Data::Ptr d( child->getChild( "d" ) );
  ASSERT_EQ( "d", d->getName() );
  ASSERT_EQ( Data::DOUBLE, d->getType() );
  ASSERT_EQ( 0, d->children().size() );
  ASSERT_EQ( 3.14, d->getDouble() );
  
  Data::Ptr s( child->getChild( "s" ) );
  ASSERT_EQ( "s", s->getName() );
  ASSERT_EQ( Data::STRING, s->getType() );
  ASSERT_EQ( 0, s->children().size() );
  ASSERT_EQ( "herro", s->getString() );
  
  ASSERT_EQ( true, data->get<bool>( "child/b" ) );
  ASSERT_EQ( -34, data->get<int>( "child/i" ) );
  ASSERT_EQ( 3.14, data->get<double>( "child/d" ) );
  ASSERT_EQ( "herro", data->get<std::string>( "child/s" ) );
  
  if ( !saveFilename.empty() )
  {
    data->set<bool>( "a/b/c/d/e", false );
    data->set<int>( "a/b/c/d/f", 12 );
    data->set<double>( "a/b/c/d/g", 41.2 );
    data->set<std::string>( "a/b/c/d/h", "world" );
    FileSystem::instance().save( data, saveFilename );
  }
  else
  {
    ASSERT_FALSE( data->get<bool>( "a/b/c/d/e" ) );
    ASSERT_EQ( 12, data->get<int>( "a/b/c/d/f" ) );
    ASSERT_EQ( 41.2, data->get<double>( "a/b/c/d/g" ) );
    ASSERT_EQ( "world", data->get<std::string>( "a/b/c/d/h" ) );
  }
}

TEST( CoreTest, xmlTest )
{
  xmlDataStreamTest( "../tests/XmlTest.xml", "XmlTest2.xml" );
  xmlDataStreamTest( "XmlTest2.xml", "" );
}
